import sys, os
from PyQt5.Qt import QApplication, QMainWindow, QDialog, QByteArray, QFileDialog, QPlainTextEdit, QFormLayout, QProgressBar, QComboBox, \
    QToolBar
from PyQt5 import QtCore
from IAPUpdTool_UI import Ui_MainWindow
from proj_qss import qss
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
from COM_UI import Ui_Dialog


class SerialPortOp(QDialog, Ui_Dialog):
    def __init__(self):
        super(SerialPortOp, self).__init__()
        self.setupUi(self)
        self.sysInit()

    def sysInit(self):
        self.updatePort()

    def getComInfo(self):
        return [self.comUICboxPort.currentText(), self.comUICboxBaudrate.currentText(), self.comUICboxDatabits.currentText(),
                self.comUICboxStopbits.currentText(), self.comUICboxParity.currentText(), self.comUICboxFlowCtl.currentText()]

    def updatePort(self):
        self.comUICboxPort.clear()
        self.comUICboxPort.addItems(self.searchComs())

    def searchComs(self):
        comList = QSerialPortInfo.availablePorts()
        comName = []
        for com in comList:
            comName.append(com.portName())
        return comName


class MainUI(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.sysInit()

    def sysInit(self):
        self.setStyleSheet(qss)
        # port
        self.portSetup = SerialPortOp()
        self.updateMainUIPort()
        self.port = QSerialPort(self)
        self.comInfoDict = {}
        self.getPortSettings()
        self.dataFrame = bytes()
        self.dataRevOK = True
        self.port.readyRead.connect(self.portRxHandler)
        self.revDataBuffer = b''
        self.readySendCodeData = {'data' : b'', 'len' : 0, 'curIdx': 0, 'total packages': 0, 'current package': 0}

        # Debug Message
        self._createDebugMessageWindow()
        self.debugMsgCnt = 0

        # 状态栏
        self._createStatusBar()

        # 工具栏
        self._createToolBars()

        #4 进度条初始化
        self.proBar.reset()
        self.proBar.setValue(0)
        self.proBar.setFormat("{}/{}".format(0, 0))
        self.proBarFlg = False

        self.sysCmd = {'enter update mode' : b'\xA5\x02\x01\x00\x0D\x0A'}

    def portRxHandler(self):
        try:
            revData = self.port.readAll()
            self.revDataBuffer += revData
            if self.guiMode.currentText() == 'Debug':
                self.DebugMessage(str(self.revDataBuffer))
            if self.revDataBuffer[-2] == b'\r' and self.revDataBuffer[-1] == b'\n':
                cmd = self.revDataBuffer[1][0]
                result = self.revDataBuffer[2][0]
                if cmd == 0x01: # 烧写程序
                    if result == 0xFE:
                        self.readySendCodeData['curIdx'] += 8
                        self.readySendCodeData['current package'] += 1
                        #4 vvv
                        self.proBar.setValue(self.readySendCodeData['current package'])
                        self.proBar.setFormat("{}/{}".format(self.readySendCodeData['current package'], self.readySendCodeData['total packages']))
                        #4 ^^^
                        self.sendData(MainUI.CmdWriteCode)
                    elif result == 0xFF:
                        self.sendData(MainUI.CmdWriteCode)
                    elif result == 0x00:
                        self.statusbar.showMessage("错误！停止更新程序!")
                    elif result == 0x01:
                        self.statusbar.showMessage("更新完成！", 10000)
                        #4 vvv
                        if self.proBarFlg == True:
                            self.readySendCodeData['current package'] += 1
                            self.proBar.setValue(self.readySendCodeData['current package'])
                            self.proBar.setFormat("{}/{}".format(self.readySendCodeData['current package'],self.readySendCodeData['total packages']))
                        #4 ^^^
                elif cmd == 0x02: # 进入升级模式
                    self.statusbar.showMessage("已进入升级模式！", 6000)

                self.revDataBuffer = b''
        except Exception as msg:
            self.revDataBuffer = b''

    def guiModeChangedSignal(self, mode):
        if mode == 'Release':
            self.debugMsgDialog.close()
        else: # Debug
            self.debugMsgDialog.show()

    def _createDebugMessageWindow(self):
        self.debugMsgDialog = QDialog(self)
        self.debugMsgDialog.setWindowTitle("Debug Message")
        self.debugMsgLayout = QFormLayout(self.debugMsgDialog)
        self.pteSysInfoDisplay = QPlainTextEdit(self.debugMsgDialog)
        self.debugMsgLayout.addWidget(self.pteSysInfoDisplay)
        self.pteSysInfoDisplay.textChanged.connect(self.pteSysInfoDisplay.ensureCursorVisible)

    def _createToolBars(self):
        self.guiModeToolBar = QToolBar("file", self)
        self.addToolBar(self.guiModeToolBar)
        self.guiMode = QComboBox()
        self.guiMode.currentTextChanged.connect(self.guiModeChangedSignal)
        self.guiMode.addItems(["Release", "Debug"])
        self.guiModeToolBar.addWidget(self.guiMode)

    def _createStatusBar(self):
        self.statusbar = self.statusBar()
        self.proBar = QProgressBar()
        self.proBar.setAlignment(QtCore.Qt.AlignCenter)
        self.statusbar.addPermanentWidget(self.proBar)

    def getPortSettings(self):
        portInfo = self.portSetup.getComInfo()
        self.comInfoDict["Port"] = portInfo[0]
        self.comInfoDict["Baud rate"] = portInfo[1]
        self.comInfoDict["Data bits"] = portInfo[2]
        self.comInfoDict["Stop bits"] = portInfo[3]
        self.comInfoDict["Parity"] = portInfo[4]
        self.comInfoDict["Flow control"] = portInfo[5]

    def updateMainUIPort(self):
        self.mainUICboxPort.clear()
        self.mainUICboxPort.addItems(self.portSetup.searchComs())

    def programmingOpSignal(self):
        obj = self.sender()
        # 显示bin文件内容
        if obj == self.mainUIBtnShowFile:
            filePath = self.mainUILeShowFile.text()
            if filePath == '':
                self.statusbar.showMessage("未选择bin文件!\r\n")
            else:
                binFile = open(filePath, 'rb')
                size = os.path.getsize(filePath)
                data = binFile.read()
                binFile.close()

                disWidget = QDialog(self)
                disWidget.setWindowTitle("bin")
                disWidget.setFixedSize(730, 500)
                layout = QFormLayout(disWidget)

                showData = QPlainTextEdit()
                showData.setReadOnly(True)

                layout.addWidget(showData)
                disWidget.setLayout(layout)

                rawNum = size // 32
                remainNum = size % 32

                addr = 0x00
                for i in range(rawNum):
                    col0 = '{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}  '.format(data[i * 32 + 0], data[i * 32 + 1], data[i * 32 + 2], data[i * 32 + 3],
                                                                                              data[i * 32 + 4], data[i * 32 + 5], data[i * 32 + 6], data[i * 32 + 7])
                    col1 = '{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}  '.format(data[i * 32 + 8], data[i * 32 + 9], data[i * 32 + 10], data[i * 32 + 11],
                                                                                              data[i * 32 + 12], data[i * 32 + 13], data[i * 32 + 14], data[i * 32 + 15])
                    col2 = '{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}  '.format(data[i * 32 + 16], data[i * 32 + 17], data[i * 32 + 18], data[i * 32 + 19],
                                                                                              data[i * 32 + 20], data[i * 32 + 21], data[i * 32 + 22], data[i * 32 + 23])
                    col3 = '{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}'.format(data[i * 32 + 24], data[i * 32 + 25], data[i * 32 + 26], data[i * 32 + 27],
                                                                                              data[i * 32 + 28], data[i * 32 + 29], data[i * 32 + 30], data[i * 32 + 31])
                    showData.insertPlainText('{:08X}  '.format(addr) + col0 + col1 + col2 + col3 + '\r\n')
                    addr += 0x20

                if remainNum != 0:
                    remainData = ""
                    for i in range(remainNum):
                        remainData += '{:02X} '.format(data[rawNum * 32 + i])
                        if (i + 1) % 8 == 0:
                            remainData += " "
                    showData.insertPlainText('{:08X}  '.format(addr) + remainData)

                disWidget.exec()
        elif obj == self.mainUIBtnUpdModel:
            self.port.write(self.sysCmd['enter update mode'])
        elif obj == self.mainUIBtnProgam:
            self.loadBinFile()
            self.revDataBuffer = b''
            self.sendData(MainUI.CmdWriteCode)
            self.statusbar.showMessage("升级程序中...", 10000)

    def sendData(self, cmd):
        if not self.port.isOpen():
            self.statusbar.showMessage("未打开串口.")
            return
        # 帧头
        sendDat = b'\xA5'
        # 命令码
        sendDat += cmd.to_bytes(length=1, byteorder='big')
        # 通信方式
        devType = 0
        if self.mianUICboxDevType.currentText() == 'STM32F103C8T6_Usart':
            devType = MainUI.STM32F103C8T6_Usart
        elif self.mianUICboxDevType.currentText() == 'FM33LG0XX_IAPBoard_USART':
            devType = MainUI.FM33LG0XX_IAPBoard_Usart
        elif self.mianUICboxDevType.currentText() == 'CAN':
            devType = MainUI.TypeCan
        sendDat += devType.to_bytes(length=1, byteorder='big')
        # 数据
        readyData = b''
        readyDataLen = 0
        remainDataLen = self.readySendCodeData['len'] - self.readySendCodeData['curIdx']
        currIdx = self.readySendCodeData['curIdx']
        if remainDataLen >= 8:
            readyDataLen = 8
            readyData = self.readySendCodeData['data'][currIdx:currIdx+8]
        elif remainDataLen == 0:
            readyDataLen = 0
            readyData = b'\x00\x00\x00\x00\x00\x00\x00\x00'
        else: # 0 < remainDataLen < 8
            readyDataLen = remainDataLen
            readyData = self.readySendCodeData['data'][currIdx:currIdx+remainDataLen]
            readyData += b'\x00' * (8 - remainDataLen)
        sendDat += readyDataLen.to_bytes(length=1, byteorder='big')
        sendDat += readyData
        # checksum
        checksum = sendDat[0] + sendDat[1] + sendDat[2] + sendDat[3] + \
                   sendDat[4] + sendDat[5] + sendDat[6] + sendDat[7] + \
                   sendDat[8] + sendDat[9] + sendDat[10] + sendDat[11]

        checksum = checksum.to_bytes(length=4, byteorder='big')
        sendDat += checksum[-1].to_bytes(length=1, byteorder='big')
        #sendDat += b'\x00'

        sendDat += b'\x0D\x0A'
        self.port.write(sendDat)

    def fileToolBtnSignal(self):
        result = QFileDialog.getOpenFileName(self, "选择bin文件", "./", "All(*.*);;bin文件(*.bin)", "bin文件(*.bin)")
        self.mainUILeShowFile.setText(result[0])
        self.loadBinFile()

    def loadBinFile(self):
        filePath = self.mainUILeShowFile.text()
        if filePath == '':
            self.statusbar.showMessage("未选择bin文件!")
            return
        self.statusbar.clearMessage()
        binFile = open(filePath, 'rb')
        data = binFile.read()
        binFile.close()
        self.readySendCodeData['data'] = data
        self.readySendCodeData['len'] = len(data)
        self.readySendCodeData['curIdx'] = 0
        if self.readySendCodeData['len'] % 8 == 0:
            self.readySendCodeData['total packages'] = self.readySendCodeData['len'] // 8
            self.proBarFlg = False
        else:
            self.readySendCodeData['total packages'] = self.readySendCodeData['len'] // 8 + 1
            self.proBarFlg = True
        self.readySendCodeData['current package'] = 0
        # 4 vvv
        self.proBar.setValue(0)
        self.proBar.setRange(0, self.readySendCodeData['total packages'])
        self.proBar.setFormat("{}/{}".format(0, self.readySendCodeData['total packages']))
        # 4 ^^^

    def portBtnSignal(self):
        obj = self.sender()
        if obj == self.mainUIBtnPortOpen:
            if self.mainUIBtnPortOpen.text() == "打开串口":
                self.comInfoDict["Port"] = self.mainUICboxPort.currentText()

                if self.comInfoDict["Port"] == '':
                    self.statusbar.showMessage("未选择COM!")
                    return
                else:
                    # Port
                    self.port.setPortName(self.comInfoDict["Port"])
                    # Baud rate
                    if self.comInfoDict["Baud rate"] == '115200':
                        self.port.setBaudRate(QSerialPort.Baud115200)
                    elif self.comInfoDict["Baud rate"] == '19200':
                        self.port.setBaudRate(QSerialPort.Baud19200)
                    elif self.comInfoDict["Baud rate"] == '9600':
                        self.port.setBaudRate(QSerialPort.Baud9600)
                    # Data bits
                    # Stop bits
                    # Parity
                    # Flow control

                    if self.port.open(QSerialPort.ReadWrite):
                        self.setPortLEDStatus(MainUI.PortIsOpen)
                        self.statusbar.showMessage("打开串口成功!", 5000)
                    else:
                        self.setPortLEDStatus(MainUI.PortIsClosed)
                        self.statusbar.showMessage("打开串口失败!",5000)
            else:
                self.port.close()
                self.setPortLEDStatus(MainUI.PortIsClosed)
        elif obj == self.mainUIBtnPortUpd:
            self.updateMainUIPort()
        elif obj == self.mainUIBtnPortSetup:
            self.portSetup.updatePort()
            selected = self.portSetup.exec()
            self.updateMainUIPort()
            if selected == QDialog.Accepted:
                self.getPortSettings()
                if self.guiMode.currentText() == 'Debug':
                    self.DebugMessage(str(self.comInfoDict) + '\r\n')
            elif selected == QDialog.Rejected:
                pass
            else:
                pass

    def DebugMessage(self, msg):
        if self.guiMode.currentText() == 'Debug':
            if type(msg) is str:
                self.debugMsgCnt += 1
                self.pteSysInfoDisplay.insertPlainText(str(self.debugMsgCnt) + '. ' + msg + '\r\n')
            elif type(msg) is int:
                self.debugMsgCnt += 1
                self.pteSysInfoDisplay.insertPlainText(str(self.debugMsgCnt) + '. ' + str(msg) + "\r\n")

    def setPortLEDStatus(self, status):
        if status == MainUI.PortIsOpen:
            self.comUIBtnLED.setStyleSheet("background-color: rgb(50, 250, 52)")
            self.mainUIBtnPortOpen.setText("关闭串口")
        else:
            self.comUIBtnLED.setStyleSheet("background-color: gray;")
            self.mainUIBtnPortOpen.setText("打开串口")

    PortIsOpen = 1
    PortIsClosed = 2
    STM32F103C8T6_Usart = 1
    FM33LG0XX_IAPBoard_Usart = 2
    TypeCan = 3

    CmdWriteCode = 1

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainUI()
    win.show()
    sys.exit(app.exec_())